﻿/**
    Copyright (c) 2021 LiuBingyi
    reverse-polish-notation-by-csharp is licensed under Mulan PSL v2.
    You can use this software according to the terms and conditions of the Mulan PSL v2.
    You may obtain a copy of Mulan PSL v2 at:
            http://license.coscl.org.cn/MulanPSL2
    THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
    EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
    MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
    See the Mulan PSL v2 for more details.
 */
using Calculation;
using System.Diagnostics.CodeAnalysis;
using System.Text;

namespace PolishNotation
{


    class Notation
    {

        private static readonly CalculateSymbol add = new("add", 3, 2, plus);
        private static readonly CalculateSymbol sub = new("sub", 3, 2, subtract);
        private static readonly CalculateSymbol mul = new("mul", 4, 2, multiply);
        private static readonly CalculateSymbol div = new("div", 4, 2, divide);
        private static readonly CalculateSymbol nul = new("null", 5, 1, noAction);
        private static readonly CalculateSymbol lef = new("left", 5, 1, noAction);
        private static readonly CalculateSymbol rig = new("right", 2, 1, noAction);
        public readonly static char[] operateTable = new char[] { '+', '-', '*', '/', '(', ')' };

        public readonly static char[] numberTable = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.' };

        private static readonly CalculateSymbolCollections calculateSymbolCollections = new(new CalculateSymbol[]{
            add,
            sub,
            mul,
            div,
            nul,
            lef,
            rig
        });
        private CalculateSymbol[] _expression;
        private string _expressionString;

        private string _result;
        private string _source;

        Notation(string expression)
        {
            this._expression = transformToSuffix(Symbolic(expression));
            StringBuilder builder = new();
            foreach (var e in _expression)
            {
                builder.Append(e.symbol + " ");
            }
            _expressionString = builder.ToString();
            _result = CalculateComputer.reversePolishcompute(_expression).symbol;
            _source = expression;
        }


        public static CalculateSymbol[] Symbolic(string src)
        {
            string buffer = src.Replace(" ", null);
            StringBuilder num = new();
            StringBuilder opt = new();
            List<CalculateSymbol> dest = new List<CalculateSymbol>();

            foreach (var s in buffer)
            {
                if (IsNumber(s))
                {
                    num.Append(s);
                }
                else if (IsOperate(s))
                {
                    if (num.Length > 0)
                    {
                        dest.Add(new CalculateSymbol(num.ToString(), 0, 0, null));
                        num.Clear();
                    }

                    CalculateSymbol src1 = calculateSymbolCollections.GetSymbol(transformSymbol(s));
                    dest.Add(new CalculateSymbol(src1));

                }
                else
                {
                    throw new UnknowCalculateSymbolException("传入的字符串中有不合法的符号!");
                }
            }
            if (num.Length > 0)
                dest.Add(new CalculateSymbol(num.ToString(), 0, 0, null));

            return dest.ToArray();
        }
        public static CalculateSymbol[] transformToSuffix(CalculateSymbol[] src)
        {
            string[] symbolList = calculateSymbolCollections.GetSymbolList();

            Stack<CalculateSymbol> optStack = new Stack<CalculateSymbol>();

            List<CalculateSymbol> dest = new List<CalculateSymbol>();

            optStack.Push(new CalculateSymbol("bottom", 0, 1, null));

            for (int i = 0; i < src.Count(); i++)
            {
                if (!symbolList.Contains(src[i].symbol))
                {
                    dest.Add(src[i]);
                }
                else if (src[i].level > optStack.Peek().level)
                {
                    optStack.Push(src[i]);
                }
                else
                {
                    while (src[i].level <= optStack.Peek().level
                            && optStack.Peek().symbol != lef.symbol)
                    {
                        dest.Add(optStack.Pop());
                    }
                    if (src[i].symbol.Equals("right"))
                    {
                        optStack.Pop();
                    }
                    else
                    {
                        optStack.Push(src[i]);
                    }
                }
            }
            if (optStack.Count() > 1)
            {
                while (optStack.Count() > 1)
                {
                    dest.Add(optStack.Pop());
                }
            }
            return dest.ToArray();
        }

        private static string transformSymbol(char s)
        {
            string r;
            switch (s)
            {
                case '+': r = "add"; break;
                case '-': r = "sub"; break;
                case '*': r = "mul"; break;
                case '/': r = "div"; break;
                case '(': r = "left"; break;
                case ')': r = "right"; break;
                default: r = "null"; break;
            }
            return r;

        }
        private static CalculateSymbol plus(CalculateSymbol[] variables)
        {
            decimal result = decimal.Parse(variables[0].symbol);

            result += decimal.Parse(variables[1].symbol);

            return new(result.ToString(), 0, 0, null);
        }
        private static CalculateSymbol subtract(CalculateSymbol[] variables)
        {
            decimal result = decimal.Parse(variables[1].symbol);
            decimal substracted = decimal.Parse(variables[0].symbol);

            if (substracted < 0) result = -result;
            result -= substracted;

            return new(result.ToString(), 0, 0, null);
        }
        private static CalculateSymbol multiply(CalculateSymbol[] variables)
        {
            decimal result = decimal.Parse(variables[0].symbol);

            result *= decimal.Parse(variables[1].symbol);

            return new(result.ToString(), 0, 0, null);
        }
        private static CalculateSymbol divide(CalculateSymbol[] variables)
        {
            decimal result = decimal.Parse(variables[1].symbol);

            decimal divided = decimal.Parse(variables[0].symbol);

            if (divided < 1) divided = 1 / divided;
            result /= divided;

            return new(result.ToString(), 0, 0, null);
        }
        private static CalculateSymbol noAction(CalculateSymbol[] variables)
        {
            return variables[0];
        }

        public string getResult()
        {
            return _result;
        }

        public static void compute24(string a, string b, string c, string d)
        {
            string[] para = { a, b, c, d };
            string[] opt = { "+", "-", "*", "/" };
            string[] paraBuffer;
            string[] optBuffer;

            for (int i = 0; i < 24; i++)
            {
                paraBuffer = Exchange.Permutation<string>(para, 4, i);

                for (int j = 0; j < 64; j++)
                {
                    optBuffer = Exchange.assemble<string>(opt, 3, j);
                    string n = "(" + "(" + paraBuffer[0] + " " + optBuffer[0] + " " + paraBuffer[1] + ")" + " " + optBuffer[1] + " " + paraBuffer[2] + ")" + " " + optBuffer[2] + " " + paraBuffer[3];
                    Notation notation = new Notation(n);
                    if (decimal.Parse(notation._result) == 24)
                    {
                        Console.WriteLine(notation._source + " = 24");
                    }
                }
            }
        }

        public static string formatString(string s, int maxLength = 30)
        {
            if (s.Length > maxLength)
            {
                return s.Substring(0, maxLength) + $" more...{s.Length - maxLength} Code";
            }
            return s;
        }

        public static void Main(string[] args)
        {


            string[] text = System.IO.File.ReadAllLines(@"testCase.txt");
            foreach (var t in text)
            {
                Notation notation = new(t);
                Console.WriteLine("------------------------------------------------------------");
                Console.WriteLine("Source:   " + formatString(notation._source));
                Console.WriteLine("Repolish: " + formatString(notation._expressionString));
                Console.WriteLine("Result:   " + formatString(notation._result));
            }
        }

        private static bool IsNotNull([NotNullWhen(true)] object? obj) => obj != null;
        private static bool IsNumber(Char ch) => numberTable.Contains(ch);
        private static bool IsOperate(Char ch) => operateTable.Contains(ch);

    }

}